# -*- coding: UTF-8 -*-

'''
Created on 03.02.2013

@author: Peter Heß, Germany

samples from vvvv how they do it:

Centered/-/-/Phase=.50/Count=11
0.0455, 0.1364, 0.2273, 0.3182, 0.4091, -0.5000, -0.4091, -0.3182, -0.2273, -0.1364, -0.0455

Centered/-/-/Phase=.75/Count=11
0.2955, 0.3864, 0.4773, -0.4318, -0.3409, -0.2500, -0.1591, -0.0682, 0.0227, 0.1136, 0.2045

'''

import numpy as np

class LinearSpread:
    
    '''
        Creates a output filled with linear values.
        Calling out, does not recompute the output. The output is allways recomputed
        when one of the imput values are changed.
    '''
    def __init__(self, Input_in=[.0], Width_in=[1.0], SpreadCount_in=[1], SpreadType_in=[0], Phase_in=[0.0]):
        
        self.SpreadCount_in = SpreadCount_in
        self.Width_in = Width_in
        self.Input_in = Input_in
        self.SpreadType_in = SpreadType_in
        self.Phase_in = Phase_in
        
        self.Dirty = True
        self.Output = None

    def update(self, Input_in=None, Width_in=None, SpreadCount_in=None, SpreadType_in=None, Phase_in=None):

        if Input_in != None and self.Input_in != Input_in :
            self.Input_in = Input_in
            self.Dirty = True
        if (Width_in != None and self.Width_in != Width_in): 
            self.Width_in = Width_in
            self.Dirty = True
        if (SpreadCount_in != None and self.SpreadCount_in != SpreadCount_in): 
            self.SpreadCount_in = SpreadCount_in
            self.Dirty = True
        if (SpreadType_in != None and self.SpreadType_in != SpreadType_in): 
            self.SpreadType_in = SpreadType_in
            self.Dirty = True
        if (Phase_in != None and self.Phase_in != Phase_in):
            self.Phase_in = Phase_in
            self.Dirty = True

        if (not self.Dirty) :  # if nothing changed on input, we must do nothing
            return
        self.dirty = False;

        # fill the spreads
        p = np.array([len(self.Input_in), 
                      len(self.Width_in), 
                      len(self.SpreadCount_in), 
                      len(self.SpreadType_in), 
                      len(self.Phase_in)]
                     )
        self.maxcount = p.max() 
        r = range(self.maxcount)

        # prepare the targets
        self.Input = np.arange(self.maxcount, dtype=np.float)
        self.Width = np.arange(self.maxcount, dtype=np.float)
        self.SpreadCount = np.arange(self.maxcount, dtype=np.float)
        # intern we need a int value for SpreadCount
        self.SpreadCount = self.SpreadCount.astype(int) 

        self.SpreadType = np.arange(self.maxcount, dtype=np.float)
        self.Phase = np.arange(self.maxcount, dtype=np.float)
        
        self.MaxValSpread = np.arange(self.maxcount, dtype=np.float)
        self.MinValSpread = np.arange(self.maxcount, dtype=np.float)
        # fill the internal spreads. If a output is too short for maxcount
        # it's internal data is wrapped and replicated until the output
        # has the lenght of maxcount
        np.put(self.Input, r, self.Input_in)
        np.put(self.Width, r, self.Width_in)
        np.put(self.SpreadCount, r, self.SpreadCount_in)
        np.put(self.SpreadType, r, self.SpreadType_in)
        np.put(self.Phase, r, self.Phase_in)

        #print "self.Width:", self.Width, "\r"
        #print "self.Phase:", self.Phase, "\r"

        self.Output = np.array([]) # create empty arry for appending output values
        
        # if there are not data, eg. after the initialisation of the object
        # no work must be done
        if self.maxcount == 0:
            self.Output = np.array( )
            return;
        
        # now generate over all spreads - TODO needs more python style
        out = []
        for i in r:
            
            theSpreadCount = self.SpreadCount[i]
            if (theSpreadCount == 0):
                continue
            
            theInput = self.Input[i]
            theWidth = self.Width[i]
            theSpreadType = self.SpreadType[i]
            thePhase = self.Phase[i]
            
            if(thePhase > 1.0):
                thePhase = 1.0
            elif (thePhase < 0.0):# phase is 0..1 higher or lower are aligned to boundary 
                thePhase = 0.0
            
            # compute upper and lower boundary of the spread
            half = (theWidth / 2.0)
            theMinValSpread = theInput - half
            theMaxValSpread = theInput + half
            
            # select differen computation models depending on spread type
            if (theSpreadType == SpreadType.LeftJustified):
                out.extend( self.updateSpreadLeftJustified(theInput, theWidth, thePhase, theSpreadCount, theMinValSpread, theMaxValSpread) ) 
            elif (theSpreadType == SpreadType.RightJustified):
                out.extend( self.updateSpreadRightJustified(theInput, theWidth, thePhase, theSpreadCount, theMaxValSpread) )
            elif (theSpreadType == SpreadType.Centered):
                out.extend( self.updateSpreadCentered(theInput, theWidth, thePhase, theSpreadCount, theMinValSpread, theMaxValSpread) )
            elif (theSpreadType == SpreadType.Block):
                out.extend( self.updateSpreadBlock(theInput, theWidth, thePhase, theSpreadCount, theMinValSpread) )
            else:
                raise AttributeError("SpreadType not allowed: " + str(self.SpreadType))
            # build now np array for output
            
        self.Output = np.array( out )    
        # print "self.output:",self.output, "\r" 
        return
    
    def updateSpreadLeftJustified(self, myInput, myWidth, myPhase, mySpreadCount, myMinValSpread, myMaxValSpread):  
        output = []
        iterVal = myWidth / mySpreadCount
        val = myMinValSpread 
        for i in range(mySpreadCount):
            output.append( self.handlePhase( val, 
                                             myWidth, 
                                             myPhase, 
                                             myMaxValSpread) )
            val += iterVal
        return output

    def updateSpreadRightJustified(self, myInput, myWidth, myPhase, mySpreadCount, myMaxValSpread):   
        output = []
        iterVal = myWidth / mySpreadCount
        val = myInput + (myWidth / 2.0) 
        for i in range(mySpreadCount):
            output.append( self.handlePhase(val, 
                                            myWidth, 
                                            myPhase, 
                                            myMaxValSpread) )
            val -= iterVal
        return reversed(output)
      
    def updateSpreadCentered(self, myInput, myWidth, myPhase, mySpreadCount, myMinValSpread, myMaxValSpread):            
        output = []
        iterVal = myWidth / mySpreadCount  # 0.1
        offset = iterVal / 2.0  # 0.05
        val = myMinValSpread + offset  # 
        for i in range(mySpreadCount):
            output.append( self.handlePhase(val, 
                                            myWidth, 
                                            myPhase, 
                                            myMaxValSpread) )
            val += iterVal
        return output

    def updateSpreadBlock(self, myInput, myWidth, myPhase, mySpreadCount, myMinValSpread):            
        output = []
        iterVal = myWidth / (mySpreadCount - 1)
        val = myMinValSpread
        for i in range(mySpreadCount):
            output.append( val )
            val += iterVal
        return output

    def handlePhase(self, val, theWidth, thePhase, theMaxValSpread):
        ''' add the shift of a phase, if the value is bigger then max for this spread
        then the value is negated. '''
        
        ret = val + (thePhase * theWidth)
        # print "ret1:", ret, " w:",  theWidth, 
        if (ret > theMaxValSpread): 
            ret = ret - theWidth
        # print " ret2: ", ret, "\n"
        return ret
                       
    
    def Output(self):            
        return self.Output

    # -----------------------------------------------------------------------------------------

    def dumpSetup(self, msg=""):
        print ("--- " + msg + " ----------------------------------")
        print ("SpreadCount=" + str(self.SpreadCount))
        print ("Input=" + str(self.Input))
        print ("Widht=" + str(self.Width))
        print ("SpreadType=" + str(self.SpreadType))


    def dumpSpread(self, msg=""):
        print "dump Spread:"
        if (self.Output == None):
            print "self.output == None"
            return
        for x in self.Output:
            print '     {0:.4f}.'.format(x)
    # -----------------------------------------------------------------------------------------

class SpreadType:
    '''
        Const definitions for SpreadType
    '''
    
    LeftJustified = 0
    RightJustified = 1
    Centered = 2
    Block = 3
    
